using System;
using System.Collections.Generic;
using System.Linq;
using Castle.DynamicProxy;
using EntityAop.Attributes;

namespace EntityAop.Interceptors.Support
{
    public static class EntityMetadataHelper
    {
        private static object SyncRoot = new object();

        private static readonly Dictionary<string, EntityMetadata> EntitiesMetadata;

        private static readonly Type _dependsOnAttributeType = typeof(DependsOnAttribute);
        private static readonly Type _affectsAttributeType = typeof(AffectsAttribute);

        static EntityMetadataHelper()
        {
            EntitiesMetadata = new Dictionary<string, EntityMetadata>();
        }

        public static EntityMetadata GetEntityMetadata(IInvocation invocation)
        {
            return EntitiesMetadata[GetKey(invocation)];
        }

        public static EntityMetadata GetEntityMetadata(Type type)
        {
            var key = GetKey(type);
            if (EntitiesMetadata.ContainsKey(key))
            {
                return EntitiesMetadata[key];
            }

            lock (SyncRoot)
            {
                var result = new EntityMetadata();

                EntitiesMetadata.Add(key, result);

                return result;
            }
        }

        private static string GetKey(IInvocation invocation)
        {
            return GetKey(InvocationHelper.GetTargetType(invocation));
        }

        private static string GetKey(Type entityType)
        {
            return entityType.FullName;
        }

        public static void InitEntityMetadata(IInvocation invocation)
        {
            var metadataKey = GetKey(invocation);

            lock (SyncRoot)
            {
                if (!EntitiesMetadata.ContainsKey(metadataKey))
                {
                    var entityMetadata = CreateEntityMetadata(invocation);

                    EntitiesMetadata.Add(metadataKey, entityMetadata);

                    entityMetadata.IsInitialized = true;
                }
                else if (!EntitiesMetadata[metadataKey].IsInitialized)
                {
                    var entityMetadata = EntitiesMetadata[metadataKey];

                    entityMetadata.Init(CreateEntityMetadata(invocation));
                }
            }
        }

        private static EntityMetadata CreateEntityMetadata(IInvocation invocation)
        {
            var result = new EntityMetadata();

            var target = InvocationHelper.GetInvocationTarget(invocation);

            foreach (var propertyInfoWithAttributes in
                        target
                        .GetType()
                        .GetProperties()
                        .Select(propertyInfo =>
                            new
                            {
                                Attributes = propertyInfo.GetCustomAttributes(_dependsOnAttributeType, true).Cast<DependsOnAttribute>(),
                                PropertyInfo = propertyInfo
                            })
                            .Where(arg => arg.Attributes != null && arg.Attributes.Any())
                    )
            {
                foreach (var attribute in propertyInfoWithAttributes.Attributes)
                {
                    result.AddDependsOnCall(propertyInfoWithAttributes.PropertyInfo, attribute.DependsOn);
                }
            }

            foreach (var methodInfoWithAttributes in
                        target
                        .GetType()
                        .GetMethods()
                        .Select(methodInfo =>
                        new
                        {
                            Attributes = methodInfo.GetCustomAttributes(_affectsAttributeType, true).Cast<AffectsAttribute>(),
                            MethodInfo = methodInfo
                        })
                            .Where(arg => arg.Attributes != null && arg.Attributes.Any()))
            {
                foreach (var attribute in methodInfoWithAttributes.Attributes)
                {
                    result.AddDependenciesOfCall(methodInfoWithAttributes.MethodInfo.Name, attribute.Dependencies);
                }
            }

            return result;
        }
    }
}